/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "generator/CapiTestcaseGenerator.h"
#include "FDModel/FDMapper.h"
#include "FDModel/FDModelManager.h"
#include "capicxx-core-tools/FrancaGeneratorExtensions.h"
#include "fmt/core.h"
#include "utilities/string_utility.h"
#include <fstream>

namespace BstIdl
{

static const std::string sInfTestHeaderTpl = R"({License}
/*
 * This file was generated by BST IDL Generator.
 * Generator Version: fidl-generator {GEN_VER}
 */

//{Interface}Test.h
#ifndef {INTERFACE}_TEST_H
#define {INTERFACE}_TEST_H

#include <gtest/gtest.h>
#include <CommonAPI/CommonAPI.hpp>
#include "{ProxyHeader}"

using namespace {NameSpace};
class {Interface}Test : public testing::Test
{
    protected:

    void SetUp()override
    {
        std::string domain = "{Domain}";
        std::string instance = "{InstanceId}";
        std::string connection = "{Interface}Test";
        runtime = CommonAPI::Runtime::get();
        myProxy = runtime->buildProxy<{Interface}Proxy>(domain, instance, connection);
        while (!myProxy->isAvailable())
            std::this_thread::sleep_for(std::chrono::microseconds(10));
        return;
    }

    void TearDown()override
    {
        return;
    }

    std::shared_ptr<{Interface}Proxy<>> myProxy;
    std::shared_ptr < CommonAPI::Runtime > runtime;
};

#endif //{INTERFACE}_TEST_H
)";

static const std::string sInfTestSourceTpl = R"({License}
/*
 * This file was generated by BST IDL Generator.
 * Generator Version: fidl-generator {GEN_VER}
 */

//{Interface}Test.cpp
#include "{Interface}Test.h"
{TestCases}
)";

// method
static const std::string sMethodSyncTestcaseTpl = R"(
TEST_F({Interface}Test, {Method}_{Index}_sync)
{
    CommonAPI::CallStatus _status;
    {Decl}
    myProxy->{SyncCall}; 
    {Check}
}
)";

static const std::string sMethodASyncTestcaseTpl = R"(
TEST_F({Interface}Test, {Method}_{Index}_async)
{
    CommonAPI::CallStatus _status;
    {Decl}
    auto fut = myProxy->{AsyncCall};
    _status = fut.get();
    {Check}
}
)";

// attribute
static const std::string sAttributeTestcaseTpl = R"(
TEST_F({Interface}Test, {Attribute}_{Index}_{Type}_sync)
{
    CommonAPI::CallStatus _status;
    {Decl}
    {ChangeEvent}
    {SyncCall} 
    {Check}
}

TEST_F({Interface}Test, {Attribute}_{Index}_{Type}_async)
{
    CommonAPI::CallStatus _status;
    {Decl}
    {ChangeEvent}
    {AsyncCall}  
    {Check}
})";

// broadcast
static const std::string sBroadcastTestcaseTpl = R"(
TEST_F({Interface}Test, {Broadcast}_{Index}_{Type})
{
    CommonAPI::CallStatus _status;
    {Decl&&Subscribe}
    {Check}
})";

static const std::string sCMakeListsTpl = R"({License}
#
# This file was generated by BST IDL Generator.
# Generator Version: fidl-generator {GEN_VER}
#

cmake_minimum_required(VERSION 3.5)

project({Interface}Test LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 14)

find_package(GTest REQUIRED)
find_package(CommonAPI 3.2.0 REQUIRED)
find_package (CommonAPI-SomeIP 3.2.0 REQUIRED)
find_package (vsomeip3 3.1.0 REQUIRED)

include_directories(
    ../src-gen/{PackagePath}
    ../src-gen
    ${COMMONAPI_INCLUDE_DIRS}
    ${COMMONAPI_SOMEIP_INCLUDE_DIRS}
    ${VSOMEIP_INCLUDE_DIRS}
)

set(PRJ_SRC_GEN_COMMONAPI_PATH ../src-gen/{PackagePath})
FILE(GLOB PRJ_PROXY_GEN_SRCS ${PRJ_SRC_GEN_COMMONAPI_PATH}/*Proxy.cpp ${PRJ_SRC_GEN_COMMONAPI_PATH}/*Deployment.cpp)

file(GLOB TEST_SRCS "*.cpp")
list(SORT TEST_SRCS)

add_executable(${PROJECT_NAME} ${TEST_SRCS} ${PRJ_PROXY_GEN_SRCS})
target_link_libraries(${PROJECT_NAME} CommonAPI CommonAPI-SomeIP GTest::Main GTest::GTest)
)";

void CapiTestcaseGenerator::initTypeMap()
{
    m_typeMap["Int8"] = "int8_t";
    m_typeMap["UInt8"] = "uint8_t";
    m_typeMap["Int16"] = "int16_t";
    m_typeMap["UInt16"] = "uint16_t";
    m_typeMap["Int32"] = "int32_t";
    m_typeMap["UInt32"] = "uint32_t";
    m_typeMap["Int64"] = "int64_t";
    m_typeMap["UInt64"] = "uint64_t";
    m_typeMap["Boolean"] = "bool";
    m_typeMap["String"] = "std::string";
    m_typeMap["Float"] = "float";
    m_typeMap["Double"] = "double";
    m_typeMap["ByteBuffer"] = "CommonAPI::ByteBuffer";
}

bool CapiTestcaseGenerator::generate()
{
    if (m_destDir.empty())
        return false;

    auto &fmodels = BstIdl::FModelManager::getInstance();
    auto &fdmodels = BstIdl::FDModelManager::getInstance();
    if (fmodels.size() == 0 && fdmodels.size() == 0)
    {
        std::cerr << "No valid models!" << std::endl;
        return false;
    }

    bool ret = true;
    for (auto model : fdmodels.getModelList())
    {
        for (auto inf : model->getInterfaceDeployments("com.bst.os.idl.test.deployment"))
        {
            if (!inf || !createDirectory(inf) || !inf->getTarget())
                continue;

            auto name = inf->getTarget()->getFQN();
            std::cout << "Generating test case header file for " << name << "...";
            if (generateHeader(inf))
            {
                std::cout << "Done!" << std::endl;
            }
            else
            {
                ret = false;
                std::cout << "Failed!" << std::endl;
            }

            std::cout << "Generating test case source file for " << name << "...";
            if (generateSource(inf))
            {
                std::cout << "Done!" << std::endl;
            }
            else
            {
                ret = false;
                std::cout << "Failed!" << std::endl;
            }

            std::cout << "Generating CMakeLists.txt ...";
            if (generateCMakeLists(inf))
            {
                std::cout << "Done!" << std::endl;
            }
            else
            {
                ret = false;
                std::cout << "Failed!" << std::endl;
            }
        }
    }

    return ret;
}

bool CapiTestcaseGenerator::createDirectory(const std::shared_ptr<FDInterface> &inf)
{
    if (!inf || !inf->getTarget())
        return false;

    // make path and get absolute folder path.
    auto path = "/";
    m_folderPath = mk_dir(m_destDir, path);
    if (m_folderPath.empty())
        return false;
    return true;
}

bool CapiTestcaseGenerator::generateHeader(const std::shared_ptr<FDInterface> &inf)
{
    if (!inf || !inf->getTarget() || m_folderPath.empty())
        return false;

    auto interface = inf->getTarget();
    auto infName = interface->getName();
    // get domain & instanceid
    std::string instanceId("");
    auto val = inf->getSingleValue("InstanceId");
    if (val && val->isString())
        instanceId = val->getString();
    if (instanceId.empty())
    {
        std::cerr << "DEPL ERROR :  The property named InstanceId should not be empty in fdepl file!" << std::endl;
        return false;
    }
    std::string domain(getSomeIPDomain(interface, instanceId));

    // create header file
    auto filename = fmt::format("{0}/{1}Test.h", m_folderPath, infName);
    std::ofstream ofs(filename, std::ofstream::out);

    // generate content
    auto content = sInfTestHeaderTpl;
    replace_all(content, "{GEN_VER}", COMMIT_HASH);
    replace_all(content, "{Interface}", infName);
    replace_all(content, "{INTERFACE}", toUpper(infName));
    replace_all(content, "{NameSpace}", interface->getNameSpace());
    replace_all(content, "{Domain}", domain);
    replace_all(content, "{InstanceId}", instanceId);
    auto proxyHeader = replace_all(interface->getNameSpace(), "::", "/");
    if (proxyHeader.empty())
        proxyHeader = infName + "Proxy.hpp";
    else
        proxyHeader += "/" + infName + "Proxy.hpp";
    replace_all(content, "{ProxyHeader}", proxyHeader);
    replace_all(content, "{License}", getLicense());

    // write file
    ofs << content;
    ofs.close();
    return true;
}
bool CapiTestcaseGenerator::generateSource(const std::shared_ptr<FDInterface> &inf)
{
    if (!inf || !inf->getTarget() || m_folderPath.empty())
        return false;
    auto model = std::dynamic_pointer_cast<FDModel>(inf->getContainer());
    if (!model)
        return false;
    auto interface = inf->getTarget();
    auto infName = interface->getName();

    // method test case generation
    std::string allTestcases;
    for (auto md : inf->getMethods())
    {
        if (!md || !md->getTarget())
            continue;
        // get test cases
        auto testcases = getTestCases(md);
        if (testcases.empty())
            continue;

        auto method = md->getTarget();
        auto methodName = method->getName();
        if (!method->getSelector().empty())
            methodName.append("_with_selector_" + method->getSelector());
        bool noFireAndForget = !method->isFireAndForget();
        std::string testcaseContent;
        for (auto iter = ++testcases.cbegin(); iter != testcases.cend(); ++iter)
        {
            auto tc = *iter;
            auto testcase = sMethodSyncTestcaseTpl;
            if (noFireAndForget)
                testcase.append(sMethodASyncTestcaseTpl);
            auto index = std::distance(testcases.cbegin(), iter);

            replace_all(testcase, "{Index}", std::to_string(index));
            replace_all(testcase, "{Decl}", getMethodArgDecl(method, inf, tc));
            replace_all(testcase, "{Check}", getMethodArgCheck(method, tc));
            testcaseContent.append(testcase);
        }
        replace_all(testcaseContent, "{Method}", methodName);
        replace_all(testcaseContent, "{SyncCall}", getMethodSyncCall(method));
        if (noFireAndForget)
            replace_all(testcaseContent, "{AsyncCall}", getMethodAsyncCall(method));
        allTestcases.append(testcaseContent);
    }

    for (auto attr : inf->getAttributes())
    {
        if (!attr || !attr->getTarget())
            continue;
        auto attribute = attr->getTarget();
        // pre
        if (attribute->isReadonly())
        {
            continue;
        }

        auto testcases = getTestCases(attr);
        if (testcases.empty())
            continue;
        std::string testcaseContent;
        for (auto iter = ++testcases.cbegin(); iter != testcases.cend(); ++iter)
        {
            auto tc = *iter;
            auto testcase = sAttributeTestcaseTpl;
            auto index = std::distance(testcases.cbegin(), iter);

            replace_all(testcase, "{Index}", std::to_string(index));
            replace_all(testcase, "{Decl}", getAttrDecl(attribute, tc));
            replace_all(testcase, "{ChangeEvent}", getAttrChanged(attribute));
            replace_all(testcase, "{Check}", getAttrCheck(attribute, tc));
            replace_all(testcase, "{Type}", getAttributeType(attribute));
            testcaseContent.append(testcase + "\n");
        }
        replace_all(testcaseContent, "{Attribute}", attribute->getName());
        replace_all(testcaseContent, "{SyncCall}", getAttributeCall(attribute));
        replace_all(testcaseContent, "{AsyncCall}", getAttributeAsyncCall(attribute));
        allTestcases.append(testcaseContent);
    }
    for (auto bc : inf->getBroadcasts())
    {
        if (!bc || !bc->getTarget())
            continue;

        auto broadcast = bc->getTarget();
        // getTestCases
        auto testcases = getTestCases(bc);
        if (testcases.empty())
            continue;
        std::string testcaseContent;
        for (auto iter = ++testcases.cbegin(); iter != testcases.cend(); ++iter)
        {
            auto tc = *iter;
            auto testcase = sBroadcastTestcaseTpl;
            auto index = std::distance(testcases.cbegin(), iter);

            replace_all(testcase, "{Index}", std::to_string(index));
            replace_all(testcase, "{Decl&&Subscribe}", getBroadcastDeclAndSubscribe(broadcast));
            replace_all(testcase, "{Check}", getBroadcastCheck(broadcast, tc));
            replace_all(testcase, "{Type}", getBroadcastType(broadcast));
            testcaseContent.append(testcase + "\n");
        }

        replace_all(testcaseContent, "{Broadcast}", broadcast->getName());
        allTestcases.append(testcaseContent);
    }
    // create source file
    auto filename = fmt::format("{0}/{1}Test.cpp", m_folderPath, infName);
    std::ofstream ofs(filename, std::ofstream::out);

    // generate content
    auto content = sInfTestSourceTpl;
    replace_all(content, "{GEN_VER}", COMMIT_HASH);
    replace_all(content, "{TestCases}", allTestcases);
    replace_all(content, "{Interface}", infName);
    replace_all(content, "{License}", getLicense());
    replace_all(content, "{License}", getLicense());

    // write file
    ofs << content;
    ofs.close();
    return true;
}

bool CapiTestcaseGenerator::generateCMakeLists(const std::shared_ptr<FDInterface> &inf)
{
    if (!inf || !inf->getTarget() || m_folderPath.empty())
        return false;

    auto interface = inf->getTarget();
    auto infName = interface->getName();
    auto packagePath = replace_all(interface->getNameSpace(), "::", "/");

    // create CMakeLists.txt file
    auto filename = fmt::format("{0}/CMakeLists.txt", m_folderPath);
    std::ofstream ofs(filename, std::ofstream::out);

    // generate content
    auto content = sCMakeListsTpl;
    replace_all(content, "{GEN_VER}", COMMIT_HASH);
    replace_all(content, "{PackagePath}", packagePath);
    replace_all(content, "{Interface}", infName);
    auto license = getLicense();
    replace_all(license, "/*", "#");
    replace_all(license, " */", "#");
    replace_all(license, " *", "#");
    replace_all(content, "{License}", license);

    // write file
    ofs << content;
    ofs.close();
    return true;
}

nlohmann::json CapiTestcaseGenerator::getTestCases(const std::shared_ptr<FDElement> &ele)
{
    auto md = std::dynamic_pointer_cast<FDMethod>(ele);
    auto bc = std::dynamic_pointer_cast<FDBroadcast>(ele);
    auto attr = std::dynamic_pointer_cast<FDAttribute>(ele);
    if (!md && !bc && !attr)
        return json::parse("");

    auto val = ele->getSingleValue("TestCases");
    if (!val || !val->isString())
    {
        return json::parse("{}");
    }
    auto raw = replace_all(val->getString(), "'", "\"");
    if (!json::accept(raw))
    {
        std::cerr << "Invalid test case format in " << val->getString() << std::endl;
        return json::parse("{}");
    }
    auto testcases = json::parse(raw);
    if (!testcases.is_array() || testcases.size() < 1)
    {
        std::cerr << "Invalid test case format in " << val->getString() << std::endl;
        return json::parse("{}");
    }

    return testcases;
}

std::string CapiTestcaseGenerator::getMethodArgDecl(const std::shared_ptr<FMethod> &method,
                                                    const std::shared_ptr<FDInterface> &inf, const nlohmann::json &tc)
{
    std::string ret;
    if (!method || !tc.is_array())
        return ret;
    // check size in test case and method definition.
    auto size = method->getInArgs().size() + method->getOutArgs().size();
    if (method->getErrorType())
        ++size;
    if (size != tc.size())
    {
        std::cerr << "TestCases sizeError: " << tc << " not match method's definition in " << method->getName()
                  << std::endl;
        return ret;
    }

    auto genExt = BstCommonAPI::FrancaGeneratorExtensions::getInstance();
    auto tcIter = tc.cbegin();
    std::list<std::string> decls;
    // args
    for (auto arg : method->getInArgs())
    {
        auto typeName = genExt.getTypeName(arg, method, true);
        auto varName = "_" + arg->getName();
        auto initializer = *tcIter++;
        typeName == "std::string"
            ? decls.emplace_back(fmt::format("    {0} {1}({2});", typeName, varName, initializer.dump()))
            : decls.emplace_back(fmt::format("    {0} {1} = {2};", typeName, varName, initializer));
    }
    for (auto arg : method->getOutArgs())
    {
        auto typeName = genExt.getTypeName(arg, method, true);
        auto varName = "_" + arg->getName();
        decls.emplace_back(fmt::format("\t{0} {1};", typeName, varName));
    }
    // err
    if (method->getErrorType())
    {
        auto typeName = genExt.getErrorNameReference(method, method->getContainer());
        decls.emplace_back(fmt::format("    {0} _error;", typeName));
    }
    ret = join(decls, "\n");

    return ret;
}

std::string CapiTestcaseGenerator::getMethodSyncCall(const std::shared_ptr<FMethod> &method)
{
    std::string ret;
    if (!method)
        return ret;

    auto genExt = BstCommonAPI::FrancaGeneratorExtensions::getInstance();
    std::list<std::string> varList;
    for (auto it : method->getInArgs())
    {
        varList.emplace_back("_" + it->getName());
    }
    varList.emplace_back("_status");
    if (genExt.hasError(method))
    {
        varList.emplace_back("_error");
    }
    for (auto it : method->getOutArgs())
    {
        varList.emplace_back("_" + genExt.getElementName(it));
    }

    ret = fmt::format("{0}({1})", method->getName(), join(varList, ", "));
    return ret;
}

std::string CapiTestcaseGenerator::getMethodAsyncCall(const std::shared_ptr<FMethod> &method)
{
    std::string ret;
    if (!method)
        return ret;

    auto genExt = BstCommonAPI::FrancaGeneratorExtensions::getInstance();
    std::list<std::string> varList;
    for (auto it : method->getInArgs())
    {
        varList.emplace_back("_" + it->getName());
    }
    auto inArgs = join(varList, ",");
    varList.clear();
    varList.emplace_back("auto status_");
    std::list<std::string> refList;
    refList.emplace_back("&_status");
    std::list<std::string> asgnList;
    asgnList.emplace_back("_status = status_;");
    if (genExt.hasError(method))
    {
        refList.emplace_back("&_error");
        varList.emplace_back("auto error_");
        asgnList.emplace_back("_error = error_;");
    }
    for (auto it : method->getOutArgs())
    {
        refList.emplace_back("&_" + it->getName());
        varList.emplace_back("auto " + it->getName() + "_");
        asgnList.emplace_back(fmt::format("_{0} = {0}_;", it->getName()));
    }
    auto callback =
        "[" + join(refList, ",") + "](" + join(varList, ",") + ")\n\t{\n\t\t" + join(asgnList, "\n\t\t") + "\n\t}";

    ret = fmt::format("{0}Async({1},{2})", method->getName(), inArgs, callback);
    return ret;
}

std::string CapiTestcaseGenerator::getMethodArgCheck(const std::shared_ptr<FMethod> &method, const json &tc)
{
    std::string ret;
    if (!method || !tc.is_array())
        return ret;
    // check size in test case and method definition.
    auto size = method->getInArgs().size() + method->getOutArgs().size();
    if (method->getErrorType())
        ++size;
    if (size != tc.size())
    {
        std::cerr << "Test case " << tc << " not match method definition in " << method->getName() << std::endl;
        return ret;
    }

    auto genExt = BstCommonAPI::FrancaGeneratorExtensions::getInstance();
    auto tcIter = tc.cbegin() + method->getInArgs().size();
    std::list<std::string> decls;
    for (auto arg : method->getOutArgs())
    {
        auto varName = "_" + arg->getName();
        auto initializer = *tcIter++;
        auto typeName = genExt.getTypeName(arg, method, true);
        if (typeName == "std::string")
            decls.emplace_back(fmt::format("\tEXPECT_EQ({0}, {1}({2}));", varName, typeName, initializer.dump()));
        else
            decls.emplace_back(fmt::format("\tEXPECT_EQ({0}, {1});", varName, initializer));
    }
    if (method->getErrorType())
    {
        auto initializer = *tcIter++;
        auto err = (*tc.crbegin()).dump();
        if (!err.empty() && err != "\"\"")
            decls.emplace_back(fmt::format("\tEXPECT_EQ(_error.toString(), std::string({0}));", err));
    }

    ret = "\n" + join(decls, "\n");
    return ret;
}

// attr generate:
std::string CapiTestcaseGenerator::getAttrDecl(const std::shared_ptr<FAttribute> &attr, const json &tc)
{
    std::string ret;
    if (!attr || tc.empty())
        return ret;

    auto genExt = BstCommonAPI::FrancaGeneratorExtensions::getInstance();
    std::string type =
        genExt.getTypeName(attr, std::dynamic_pointer_cast<BstIdl::FModelElement>(attr->getContainer()), true);
    ret = type + " _value" + ";\n";
    if (!attr->isReadonly())
    {
        type == "std::string" ? ret.append(fmt::format("\t" + type + " requestValue({0});\n", tc.dump()))
                              : ret.append(fmt::format("\t" + type + " requestValue = {0};\n", tc));
        ret.append("\t" + type + " responseValue;\n");
        ret.append("\tCommonAPI::CallStatus _callStatus;");
    }
    return ret;
}

std::string CapiTestcaseGenerator::getAttrChanged(const std::shared_ptr<FAttribute> &attr)
{
    std::string ret;
    if (!attr || attr->isNoSubscriptions())
        return " ";
    auto attr_name = toFirstUpper(attr->getName());
    auto genExt = BstCommonAPI::FrancaGeneratorExtensions::getInstance();
    auto type = genExt.getTypeName(attr, std::dynamic_pointer_cast<BstIdl::FModelElement>(attr->getContainer()), true);

    ret.append(fmt::format("{0} changeVal;", type));
    ret.append(fmt::format("\n\n\tmyProxy->get{0}Attribute().getChangedEvent().subscribe([&changeVal](const {1}& val)",
                           attr_name, type));
    ret.append(
        "\n\t{\n\t\tstd::cout <<\"Received change message: \" << val << std::endl;\n\t\tchangeVal = val;\n\t});");
    return ret;
}

std::string CapiTestcaseGenerator::getAttributeCall(const std::shared_ptr<FAttribute> &attr)
{
    std::string ret;
    if (!attr)
        return ret;
    // set&get Value
    auto attr_name = toFirstUpper(attr->getName());
    if (!attr->isReadonly())
        ret.append(
            fmt::format("myProxy->get{0}Attribute().setValue(requestValue, _callStatus, responseValue);", attr_name));

    ret.append(fmt::format("\n\tmyProxy->get{0}Attribute().getValue(_status, _value);", attr_name));
    return ret;
}

std::string CapiTestcaseGenerator::getAttributeAsyncCall(const std::shared_ptr<FAttribute> &attr)
{
    std::string ret;
    if (!attr)
        return ret;
    auto attr_name = toFirstUpper(attr->getName());
    auto genExt = BstCommonAPI::FrancaGeneratorExtensions::getInstance();
    auto type = genExt.getTypeName(attr, std::dynamic_pointer_cast<BstIdl::FModelElement>(attr->getContainer()), true);

    // set&get Value
    if (!attr->isReadonly())
    {
        ret.append(fmt::format("auto getFut = myProxy->get{0}Attribute().setValueAsync(requestValue, [&_callStatus, "
                               "&responseValue](const CommonAPI::CallStatus &callStatus, const {1}& val)",
                               attr_name, type));
        ret.append(
            "\n\t{\n\t\tstd::cout <<\"attribute's setValueAsync message: \" << val << std::endl;\n\t\t_callStatus = "
            "callStatus; \n\t\tresponseValue = val;\n\t});");
        ret.append("\n\t_callStatus = getFut.get();");
    }

    ret.append(fmt::format("\n\tauto setFut = myProxy->get{0}Attribute().getValueAsync([&_status, &_value](const "
                           "CommonAPI::CallStatus &callStatus, const {1}& val)",
                           attr_name, type));
    ret.append("\n\t{\n\t\tstd::cout <<\"attribute's getValueAsync message: \" << val << std::endl;\n\t\t_status = "
               "callStatus; "
               "\n\t\t_value = val;\n\t});");
    ret.append("\n\t_status = setFut.get();");
    return ret;
}

std::string CapiTestcaseGenerator::getAttrCheck(const std::shared_ptr<FAttribute> &attr, const json &tc)
{
    std::string ret;
    if (!attr || tc.empty())
        return ret;

    auto genExt = BstCommonAPI::FrancaGeneratorExtensions::getInstance();
    std::string type =
        genExt.getTypeName(attr, std::dynamic_pointer_cast<BstIdl::FModelElement>(attr->getContainer()), true);
    std::string val;
    type == "std::string" ? val.append("std::string(" + tc.dump() + ")") : val.append(tc);

    if (!attr->isNoSubscriptions() && !attr->isReadonly())
        ret.append(fmt::format("\n\tEXPECT_EQ(changeVal, {0});", val));
    if (!attr->isReadonly())
        ret.append(fmt::format("\n\tEXPECT_EQ(responseValue, {0});", val));
    ret.append(fmt::format("\n\tEXPECT_EQ(_value, {0});", val));
    return ret;
}

std::string CapiTestcaseGenerator::getAttributeType(const std::shared_ptr<FAttribute> &attr)
{
    std::string className = "Attribute";
    if (attr->isReadonly())
    {
        className = "Readonly" + className;
    }
    if (!attr->isNoSubscriptions())
    { // isObservable
        className = "Observable" + className;
    }
    return className;
}

std::string CapiTestcaseGenerator::getBroadcastDeclAndSubscribe(const std::shared_ptr<FBroadcast> &broadcast)
{
    if (broadcast->getOutArgs().empty())
        return std::string();
    std::string subscribe;
    auto genExt = BstCommonAPI::FrancaGeneratorExtensions::getInstance();
    std::list<std::string> decls;
    std::list<std::string> paras;
    std::list<std::string> refs;
    std::list<std::string> assignments;

    // prepare args
    for (const auto &arg : broadcast->getOutArgs())
    {
        auto typeName = genExt.getTypeName(arg, broadcast, true);
        auto paraName = arg->getName() + "_";
        auto declName = "_" + arg->getName();

        decls.emplace_back(fmt::format("\t{0} {1};", typeName, declName));
        paras.emplace_back(fmt::format("const {0}& {1}", typeName, paraName));
        refs.emplace_back(fmt::format("&{0}", declName));
        assignments.emplace_back(fmt::format("{0} = {1};", declName, paraName));
    }

    subscribe.append(join(decls, "\n").substr(1));
    if (broadcast->isSelective())
        subscribe.append(fmt::format("\n\n\tmyProxy->get{0}SelectiveEvent().subscribe([{1}]({2})",
                                     toFirstUpper(broadcast->getName()), join(refs, ", "), join(paras, ", ")));
    else
        subscribe.append(fmt::format("\n\n\tmyProxy->get{0}Event().subscribe([{1}]({2})",
                                     toFirstUpper(broadcast->getName()), join(refs, ", "), join(paras, ", ")));
    subscribe.append("\n\t{\n\t\t" + join(assignments, "\n\t\t") + "\n\t});");
    return subscribe;
}

std::string CapiTestcaseGenerator::getBroadcastCheck(const std::shared_ptr<FBroadcast> &broadcast, const json &tc)
{
    std::string ret;
    if (!broadcast || tc.empty())
        return ret;
    // check size in test case and method definition.
    auto size = broadcast->getOutArgs().size();
    if (size != tc.size())
    {
        std::cerr << "Test case " << tc << " not match broadcast definition in " << broadcast->getName() << std::endl;
        return ret;
    }

    auto tcIter = tc.cbegin();
    std::list<std::string> decls;
    auto genExt = BstCommonAPI::FrancaGeneratorExtensions::getInstance();
    for (auto arg : broadcast->getOutArgs())
    {
        auto varName = "_" + arg->getName();
        auto initializer = *tcIter++;
        auto typeName = genExt.getTypeName(arg, broadcast, true);

        if (typeName == "std::string")
            decls.emplace_back(fmt::format("\tEXPECT_EQ({0}, {1}({2}));", varName, typeName, initializer.dump()));
        else
            decls.emplace_back(fmt::format("\tEXPECT_EQ({0}, {1});", varName, initializer));
    }

    ret = "\n" + join(decls, "\n");
    return ret;
}

std::string CapiTestcaseGenerator::getBroadcastType(const std::shared_ptr<BstIdl::FBroadcast> &bc)
{
    std::string className = "Broadcast";

    if (bc->isSelective())
    {
        className = "Selective" + className;
    }

    return className;
}

std::string CapiTestcaseGenerator::getSomeIPDomain(const std::shared_ptr<BstIdl::FInterface> &target,
                                                   const std::string &instanceId)
{
    std::shared_ptr<BstIdl::FDExtensionRoot> provider = nullptr;
    auto &mgr = BstIdl::FDModelManager::getInstance();
    for (const auto &model : mgr.getModelList())
    {
        provider = model->getProviderDeployment("org.genivi.commonapi.someip.deployment");
        if (provider != nullptr)
            break;
    }
    if (provider == nullptr)
        return std::string("");

    for (const auto &ins : provider->getInstances())
    {
        auto id_val = ins->getSingleValue("InstanceId");
        if (std::dynamic_pointer_cast<BstIdl::FInterface>(ins->getTarget()) == target && id_val && id_val->isString() &&
            id_val->getString() == instanceId)
        {
            auto val = ins->getSingleValue("Domain");
            if (!val || !val->isString())
            {
                auto ret = std::find_if(ins->getDefaultProps().cbegin(), ins->getDefaultProps().cend(),
                                        [](auto item) { return item->getName() == "Domain"; });
                if (ret != ins->getDefaultProps().cend())
                    return "local";
                else
                {
                    std::cerr << "something wrong with property \"Domain\" of interface deployment for specification "
                                 "com.bst.os.idl.test.deployment."
                              << std::endl;
                    return std::string("");
                }
            }
            return val->getString();
        }
    }
    return std::string("");
}

} // namespace BstIdl